Frontend.ctrl.forum = function() {
	var store = {

	// filter default option
	}, _filter = function(self) {
		var i = self,
			s = i.parent().find('span'),
			v = i.val();

		if(v.length > 0) s.hide();
		else s.fadeIn('fast');

	// bind for filter
	}, fields = function() {
		$('input[type="text"], textarea', '#forum').each(function() {
			var self = $(this);

			_filter(self);
			self.on('input', function(){ _filter(self); });
		});

	// topic actions
	}, bbcode = function() {
		// wrap bbocde in selected text
		function _wrap(el, open, close) {
		    var start = el[0].selectionStart,
		    	end = el[0].selectionEnd;

			// update value
		    el.val(
		    	el.val().substring(0, start) + open +
		    	el.val().substring(start, end) + close +
		    	el.val().substring(end, el.val().length)
		    );
		}

		// on button click
		$('a', 'div.bbcode').on('click', function(e) {
			e.preventDefault();
			var b = $(this),
				self = $('textarea', '#forum');

			_wrap(self, '[' + b.data('handle') + ']', '[/' + b.data('handle') + ']');
			_filter(self);
		});

	// post new topic
	}, post = function() {
		$('form.new_topic', '#forum').on('submit', function(e){
			e.preventDefault();
			Frontend.go(window.location.href.replace('category', 'post'));
		});

		// on post
		$('#form_topic', '#forum').on('submit', function(e){
			e.preventDefault();
			var post = $(this).serialize(),
				b = $('button', '#form_topic');

			// for every form field
			$('input, textarea', '#form_topic').each(function() {
				var o = $(this);
					n = o.attr('name'),
					N = n.charAt(0).toUpperCase() + n.substr(1);

				// remove errors
				o.removeClass('error');

				// check if blank
				if(!$.trim(o.val()).length > 0) {
					Frontend.noty({type: 'error', text: N +' is blank'});
					o.addClass('error');
					o.val('');
					_filter(o);

				// filled?
				} else {
					// check valid email
					if(o.attr('name') == 'email' && !validate(o.val())) {
						Frontend.noty({type: 'error', text: N +' is invalid'});
						o.addClass('error');
					}
				}
			});

			// check for errors
			var error = false;
			$('input, textarea', '#form_topic').each(function() {
				if($(this).hasClass('error'))
					error = true;
			});

			// stop if errors found
			if(error)
				return false;

			// do post procedure
			b.attr('disabled', 'disabled');
			Frontend.ajax({type: 'post', data: 'id=' + b.data('id') + '&' + post, url: 'forum/create', success: function(data){
				b.removeAttr('disabled');

				// send message
				Frontend.noty({type: 'success', text: 'Topic created!'});
				Frontend.socket.noty({type: 'alert', text: data.name + ' created a new forum topic!'});

				// announce + redirect
				Frontend.socket.announce(data.announce);
				Frontend.go('forum/topic/'+ data.link);

				// listen for the lick event
				$('div.page', 'div.site').bind('licked', function() {
					// now unbind & alert change
					$(this).unbind('licked');
					$('tr#reply_'+ data.id, '#forum').flare();
				});
			}});
		});

	// focus post textarea
	}, focusPostTextarea = function() {
		$('textarea', '#form_reply').focus();

	// topic actions
	}, topic = function() {
		// on button click
		$('button', 'form.topic_handlers').on('click', function(e) {
			e.preventDefault();
			var b = $(this);

			// reply
			if(b.data('handle') == 'reply') {
				resetReply();

				// scroll to reply form
				if($(window).scrollTop() + $(window).height() == Frontend.height()) focusPostTextarea();
				else $("html, body").animate({scrollTop: $(document).height() - $(window).height()}, 200, 'swing').promise().done(focusPostTextarea);

			// admin buttons
			} else {
				b.attr('disabled', 'disabled');
				Frontend.ajax({type: 'post', data: {id: b.data('id')}, url: 'forum/' + b.data('handle'), success: function(data){
					b.removeAttr('disabled');

					// send noty
					Frontend.noty({type: 'success', text: data.noty});
					if(data.warn)
						Frontend.noty({type: 'warning', text: data.warn});

					// tokens? update rank?
					if(data.tokens)
						Frontend.socket.rank(data.tokens);

					// broadcast announce and redirect
					Frontend.socket.announce(data.announce);
					Frontend.go(window.location.href);
				}});
			}
		});

	// reset reply to post
	}, resetReply = function(e) {
		if(e) e.preventDefault();
		$('button.reply_cancel', '#form_reply').remove();

		var t = $('textarea', '#form_reply'),
			b = $('button', '#form_reply');

		t.val('');
		t.removeClass('error');
		_filter(t);
		b.html('Post').data('edit-id', '');

	// reply actions
	}, reply = function() {
		// on quote
		$('a.reply_quote', '#forum').on('click', function(e) {
			e.preventDefault();
			resetReply();

			// get vars
			var b = $('button', '#form_reply'),
				t = $('textarea', '#form_reply');

			// scroll to reply form
			if($(window).scrollTop() + $(window).height() == Frontend.height()) focusPostTextarea();
			else $("html, body").animate({scrollTop: $(document).height() - $(window).height()}, 200, 'swing').promise().done(focusPostTextarea);

			b.attr('disabled', 'disabled');
			Frontend.ajax({type: 'post', data: 'id=' + $(this).data('id'), url: 'forum/get', success: function(data){
				b.removeAttr('disabled');

				// fill out post form
				t.val('[quote='+ data.author +']'+ data.message +'[/quote]');
				_filter(t);
			}});
		});

		// on edit
		$('a.reply_edit', '#forum').on('click', function(e) {
			e.preventDefault();
			resetReply();

			// get vars
			var b = $('button', '#form_reply'),
				t = $('textarea', '#form_reply');

			// scroll to reply form
			if($(window).scrollTop() + $(window).height() == Frontend.height()) focusPostTextarea();
			else $("html, body").animate({scrollTop: $(document).height() - $(window).height()}, 200, 'swing').promise().done(focusPostTextarea);

			b.attr('disabled', 'disabled');
			Frontend.ajax({type: 'post', data: 'id=' + $(this).data('id'), url: 'forum/get', success: function(data){
				b.removeAttr('disabled');

				// fill out post form
				t.val(data.message);
				_filter(t);
				b.html('Edit').data('edit-id', data.id);
				$("<button class='reply_cancel'>Cancel</button>").insertBefore(b);
			}});
		});

		// on cancel
		$('#forum').on('click', "button.reply_cancel", resetReply);

		// on delete
		$('a.reply_delete', '#forum').on('click', function(e) {
			e.preventDefault();
			var self = $(this);

			// show confirm
			Frontend.noty({
				type: 'warning',
				text: 'Are you sure?',
				buttons: [{text: 'Yes', onClick: function($noty) {
					$noty.close();

					Frontend.ajax({type: 'post', data: 'id=' + self.data('id'), url: 'forum/delete', success: function(data){
						// back to category?
						var back = data.link.substr(0, 9) == 'category/' ? true : false;

						// did we delete the topic?
						if(back) {
							// send success message
							Frontend.noty({type: 'success', text: 'Topic deleted!'});
							Frontend.socket.noty({type: 'alert', text: data.name + ' deleted a forum topic!'});

						// we just deleted a reply
						} else {
							// send success message
							Frontend.noty({type: 'success', text: 'Reply deleted!'});
							Frontend.socket.noty({type: 'alert', text: data.name + ' deleted a forum reply!'});
						}

						// broadcast announcement
						Frontend.socket.announce(data.announce);

						// redirect
						Frontend.go(back ? 'forum/'+ data.link : window.location.href);
					}});
				}}, {text: 'No', onClick: function($noty) {
					$noty.close();
				}}]
			});
		});

		// on post
		$('#form_reply', '#forum').on('submit', function(e){
			e.preventDefault();
			var post = $(this).serialize(),
				b = $('button:last', '#form_reply'),
				edit = b.data('edit-id') ? true : false;

			// for every form field
			$('textarea', '#form_reply').each(function() {
				var o = $(this);
					n = o.attr('name'),
					N = n.charAt(0).toUpperCase() + n.substr(1);

				// remove errors
				o.removeClass('error');

				// check if blank
				if(!$.trim(o.val()).length > 0) {
					Frontend.noty({type: 'error', text: N +' is blank'});
					o.addClass('error');
					o.val('');
					_filter(o);

				// filled?
				} else {
					// check valid email
					if(o.attr('name') == 'email' && !validate(o.val())) {
						Frontend.noty({type: 'error', text: N +' is invalid'});
						o.addClass('error');
					}
				}
			});

			// check for errors
			var error = false;
			$('textarea', '#form_reply').each(function() {
				if($(this).hasClass('error'))
					error = true;
			});

			// stop if errors found
			if(error)
				return false;

			// do post procedure
			b.attr('disabled', 'disabled');
			Frontend.ajax({type: 'post', data: 'id=' + b.data(edit ? 'edit-id' : 'id') + '&' + post, url: 'forum/' + (edit ? 'edit' : 'reply'), success: function(data){
				b.removeAttr('disabled');

				// on edit
				if(edit) {
					var r = $('tr#reply_'+ b.data('edit-id'), '#forum');

					// scroll to element
					if(!Frontend.pinpoint(r))
						$("html, body").scrollTop((r.offset().top - 10) - $("html, body").offset().top);

					// update msg
					r.find('div.msg').html(data.message);
					images();

					// send message
					Frontend.noty({type: 'success', text: 'Reply edited!'});
					Frontend.socket.noty({type: 'alert', text: data.name + ' edited a forum reply!'});

					// announce
					Frontend.socket.announce(data.announce);

					// reset and alert change
					resetReply();
					r.flare();

				// on post
				} else {
					// send message
					Frontend.noty({type: 'success', text: 'Reply posted!'});
					Frontend.socket.noty({type: 'alert', text: data.name + ' posted a new forum reply!'});

					// announce + redirect
					Frontend.socket.announce(data.announce);
					Frontend.go('forum/topic/'+ data.link);

					// listen for the lick event
					$('div.page', 'div.site').bind('licked', function() {
						// scroll to bottom
						if(!($(window).scrollTop() + $(window).height() == Frontend.height()))
							$("html, body").scrollTop($(document).height() - $(window).height());

						// now unbind & alert change
						$(this).unbind('licked');
						$('tr#reply_'+ data.id, '#forum').flare();
					});
				}
			}});
		});

	// handle remote images
	}, images = function() {
		$('div.msg a.picture:not(.done) > img', 'div.site').each(function() {
			var self = $(this),
				msg = self.closest('div.msg'),
				a = self.parent(),
				width = msg.width();

			// add loading
			a.addClass('loading');

			// on load
			this.onload = function() {
				// resize if width bigger than parent
				if(this.width > width) {
					a.prepend('<div class="resize">This image was resized to fit into the layout. Click on it to view the original.</div>');
					self.attr('width', width);
				}

				// remove loading and add done
				a.removeClass('loading').addClass('done');
			};
		});

	// pagination
	}, pagination = function() {
		// stop clicking on inactive links
		$('ul.pagination > li > a.inactive').on('click', function(e) {
			e.preventDefault();
		});

	// on announce from forum
	}, announce = function(result) {
		// default announce
		if(Frontend.announce(result)) {
			// not in topic? proceed with default
			if(Frontend.location.path.func != 'topic')
				Frontend.go(window.location.href);

			// in topic? custom messages!
			else {
				// already got a temporary notify? close that first
				if(window.TEMP_NOTY)
					window.TEMP_NOTY.close();

				// switch on mod
				switch(result.data.mod) {
					// reply was added
					case '+':
						window.TEMP_NOTY = Frontend.noty({
							type: 'information',
							text: 'New replies detected.<br />Go to latest posts?',
							buttons: [{text: 'Yes', onClick: function($noty) {
								$noty.close();
								Frontend.go('forum/topic/' + result.data.last_page);

								// listen for the lick event
								$('div.page', 'div.site').bind('licked', function() {
									// scroll to bot
									if(!($(window).scrollTop() + $(window).height() == Frontend.height()))
										$("html, body").animate({scrollTop: $(document).height() - $(window).height()}, 200, 'swing');

									// unbind :(
									$(this).unbind('licked');
								});
							}}, {text: 'No', onClick: function($noty) {
								$noty.close();
							}}]
						});
					break;

					// reply was edited
					case '~':
						// make sure the edit is visible on page
						if(!$('tr#reply_'+ result.data.reply_id, '#forum').length > 0)
							return;

						window.TEMP_NOTY = Frontend.noty({
							type: 'information',
							text: 'A reply was edited.<br />Update and go to it?',
							buttons: [{text: 'Yes', onClick: function($noty) {
								$noty.close();
								Frontend.go(window.location.href);

								// listen for the lick event
								$('div.page', 'div.site').bind('licked', function() {
									var r = $('tr#reply_'+ result.data.reply_id, '#forum');

									// scroll to element and highlight it (sorta)
									if(!Frontend.pinpoint(r)) {
										$("html, body").animate({scrollTop: (r.offset().top - 10) - $("html, body").offset().top}, 200, 'swing').promise().done(function() {
											r.flare();
										});
									} else r.flare();

									// unbind :(
									$(this).unbind('licked');
								});
							}}, {text: 'No', onClick: function($noty) {
								$noty.close();
							}}]
						});
					break;

					// reply was deleted
					case '-':
						// make sure the edit is visible on page
						if(!$('tr#reply_'+ result.data.reply_id, '#forum').length > 0)
							return;

						window.TEMP_NOTY = Frontend.noty({
							type: 'information',
							text: 'A reply was deleted.<br />Refresh topic?',
							buttons: [{text: 'Yes', onClick: function($noty) {
								$noty.close();
								Frontend.go(window.location.href);
							}}, {text: 'No', onClick: function($noty) {
								$noty.close();
							}}]
						});
					break;

					// topic was deleted
					case '!':
						window.TEMP_NOTY = Frontend.noty({
							type: 'information',
							text: 'This topic was deleted.<br />Return to topic list?',
							buttons: [{text: 'Yes', onClick: function($noty) {
								$noty.close();
								Frontend.go('forum/category/' + result.data.back_page);
							}}, {text: 'No', onClick: function($noty) {
								$noty.close();
							}}]
						});
					break;
				}
			}
		}

	// constructor
	}, __construct = function() {
		bbcode();
		post();
		topic();
		reply();
		fields();
		images();
		pagination();
	};

	// public, yay
	return {
		init: __construct,
		announce: announce,
		images: images
	};
}();